#include "stdafx.h"

#include "hwinperlinnoice.h"

namespace harlinn
{
    namespace windows
    {
        namespace
        {
            int _grad3[12][3] = 
            { 
                {1,1,0}, {-1,1,0}, {1,-1,0}, {-1,-1,0},
                {1,0,1},{-1,0,1},{1,0,-1},{-1,0,-1},
                {0,1,1},{0,-1,1},{0,1,-1},{0,-1,-1}
            };

            int _initPerm[] = 
            {
                151, 160, 137, 91, 90, 15, 131, 13, 201, 95, 96, 53, 194, 233, 7, 225, 140, 36, 103, 30, 69, 
                142, 8, 99, 37, 240, 21, 10, 23, 190,  6, 148, 247, 120, 234, 75, 0, 26, 197, 62, 94, 252, 219, 
                203, 117, 35, 11, 32, 57, 177, 33, 88, 237, 149, 56, 87, 174, 20, 125, 136, 171, 168,  68, 175, 
                74, 165, 71, 134, 139, 48, 27, 166, 77, 146, 158, 231, 83, 111, 229, 122, 60, 211, 133, 230, 
                220, 105, 92, 41, 55, 46, 245, 40, 244, 102, 143, 54,  65, 25, 63, 161,  1, 216, 80, 73, 209, 
                76, 132, 187, 208,  89, 18, 169, 200, 196, 135, 130, 116, 188, 159, 86, 164, 100, 109, 198, 173, 
                186,  3, 64, 52, 217, 226, 250, 124, 123, 5, 202, 38, 147, 118, 126, 255, 82, 85, 212, 207, 206, 
                59, 227, 47, 16, 58, 17, 182, 189, 28, 42, 223, 183, 170, 213, 119, 248, 152,  2, 44, 154, 163,  
                70, 221, 153, 101, 155, 167,  43, 172, 9, 129, 22, 39, 253,  19, 98, 108, 110, 79, 113, 224, 232, 
                178, 185,  112, 104, 218, 246, 97, 228, 251, 34, 242, 193, 238, 210, 144, 12, 191, 179, 162, 241,  
                81, 51, 145, 235, 249, 14, 239, 107, 49, 192, 214,  31, 181, 199, 106, 157, 184,  84, 204, 176, 
                115, 121, 50, 45, 127,  4, 150, 254, 138, 236, 205, 93, 222, 114, 67, 29, 24, 72, 243, 141, 128, 
                195, 78, 66, 215, 61, 156, 180
            };

        
            int _perm[512] = {0,};

            bool permInitialize() 
            {
                for (int i = 0; i < 512; i++) 
                {
                    _perm[i] = _initPerm[i & 255];
                }
                return true;
            }

            bool permInitialized = permInitialize(); 
                 

            inline int Floor(double x) 
            {
                return x > 0 ? (int)x : (int)x - 1;
            }

            inline double Dot(int* g, double x, double y, double z) 
            {
                return g[0] * x + g[1] * y + g[2] * z;
            }

            inline double Fade(double t) 
            {
                return t * t * t * (t * (t * 6 - 15) + 10);
            }

            inline double Mix(double a, double b, double t) 
            {
                return (1 - t) * a + t * b;
            }

                
            inline double Noise(double x, double y, double z) 
            {
                int X = Floor(x);
                int Y = Floor(y);
                int Z = Floor(z);

                x = x - X;
                y = y - Y;
                z = z - Z;

                X = X & 255;
                Y = Y & 255;
                Z = Z & 255;

                int gi000 = _perm[X + _perm[Y + _perm[Z]]] % 12;
                int gi001 = _perm[X + _perm[Y + _perm[Z + 1]]] % 12;
                int gi010 = _perm[X + _perm[Y + 1 + _perm[Z]]] % 12;
                int gi011 = _perm[X + _perm[Y + 1 + _perm[Z + 1]]] % 12;
                int gi100 = _perm[X + 1 + _perm[Y + _perm[Z]]] % 12;
                int gi101 = _perm[X + 1 + _perm[Y + _perm[Z + 1]]] % 12;
                int gi110 = _perm[X + 1 + _perm[Y + 1 + _perm[Z]]] % 12;
                int gi111 = _perm[X + 1 + _perm[Y + 1 + _perm[Z + 1]]] % 12;

                double n000 = Dot(_grad3[gi000], x, y, z);
                double n100 = Dot(_grad3[gi100], x - 1, y, z);
                double n010 = Dot(_grad3[gi010], x, y - 1, z);
                double n110 = Dot(_grad3[gi110], x - 1, y - 1, z);
                double n001 = Dot(_grad3[gi001], x, y, z - 1);
                double n101 = Dot(_grad3[gi101], x - 1, y, z - 1);
                double n011 = Dot(_grad3[gi011], x, y - 1, z - 1);
                double n111 = Dot(_grad3[gi111], x - 1, y - 1, z - 1);

                double u = Fade(x);
                double v = Fade(y);
                double w = Fade(z);

                double nx00 = Mix(n000, n100, u);
                double nx01 = Mix(n001, n101, u);
                double nx10 = Mix(n010, n110, u);
                double nx11 = Mix(n011, n111, u);

                double nxy0 = Mix(nx00, nx10, v);
                double nxy1 = Mix(nx01, nx11, v);

                double nxyz = Mix(nxy0, nxy1, w);

                return nxyz;
            }

            double Generate(double x, double y, double z, double amplitude, double frequency, double persistence, int octaves) 
            {
                double noiseValue = 0;
                for (int i = 0; i < octaves; i++) 
                {
                    noiseValue += Noise(x * frequency, y * frequency, z * frequency) * amplitude;
                    frequency *= 2;
                    amplitude *= persistence;
                }

                noiseValue /=  octaves;
                return noiseValue;
            }

            int NoiceToColor(double noiseValue)
            {
                if (noiseValue < 0) 
                {
                    noiseValue = 0;
                }
                else if (noiseValue > 1) 
                {
                    noiseValue = 1;
                }

                return (int) (noiseValue * 255);
            }

            void GenerateNoise(int* pixels, int width, int height, double amplitude, double frequency, double persistence, int octaves, double zStart) 
            {
                for (int x = 0; x < width; x++) 
                {
                    for (int y = 0; y < height; y++) 
                    {
                        int r = NoiceToColor(Generate(x, y, zStart, amplitude, frequency, persistence, octaves));
                        int g = NoiceToColor(Generate(x, y, zStart + 100, amplitude, frequency, persistence, octaves));
                        int b = NoiceToColor(Generate(x, y, zStart + 200, amplitude, frequency, persistence, octaves));

                        pixels[y * width + x] = (255 << 24) + (r << 16) + (g << 8) + b;
                    }
                }
            }
        }


        HWIN_EXPORT PerlinNoice::PerlinNoice()
            : pixels(nullptr),
              width(0),
              height(0),
              amplitude(1.2), 
              frequency(0.020),
              persistence(0.9),
              octaves(3),
              zStart(0)
        {

        }


        HWIN_EXPORT PerlinNoice::PerlinNoice(int theWidth, int theHeight,double theAmplitude, double theFrequency, double thePersistence, int theOctaves, double theZStart)
            : pixels(nullptr),
              width(theWidth),
              height(theHeight),
              amplitude(theAmplitude), 
              frequency(theFrequency),
              persistence(thePersistence),
              octaves(theOctaves),
              zStart(theZStart)
        {

        }

        HWIN_EXPORT PerlinNoice::PerlinNoice(const PerlinNoice& other)
            : pixels(nullptr),
              width(other.width),
              height(other.height),
              amplitude(other.amplitude), 
              frequency(other.frequency),
              persistence(other.persistence),
              octaves(other.octaves),
              zStart(other.zStart)
        {
            if(other.pixels)
            {
                size_t size = width*height;
                pixels = new int[size];
                memcpy(pixels,other.pixels,size);
            }
        }

        HWIN_EXPORT PerlinNoice::PerlinNoice(PerlinNoice&& other)
            : pixels(other.pixels),
              width(other.width),
              height(other.height),
              amplitude(other.amplitude), 
              frequency(other.frequency),
              persistence(other.persistence),
              octaves(other.octaves),
              zStart(other.zStart)
        {
            if(other.pixels)
            {
                other.pixels = nullptr;    
            }
        }


        HWIN_EXPORT PerlinNoice::~PerlinNoice()
        {
            if(pixels)
            {
                delete[] pixels;
            }
        }

        HWIN_EXPORT PerlinNoice& PerlinNoice::operator = (const PerlinNoice& other)
        {
            size_t oldSize = width*height;
            width = other.width;
            height = other.height;
            amplitude = other.amplitude;
            frequency = other.frequency;
            persistence = other.persistence;
            octaves = other.octaves;
            zStart = other.zStart;

            if((other.pixels)&&(other.pixels != pixels))
            {
                size_t newSize = width*height;

                if( pixels )
                {
                    if(newSize != oldSize)
                    {
                        delete[] pixels;
                        pixels = new int[newSize];
                    }
                }
                else
                {
                    pixels = new int[newSize];
                }
                memcpy(pixels,other.pixels,newSize);
            }
            else if(pixels)
            {
                delete[] pixels;
                pixels = nullptr;
            }
            return *this;
        }

        HWIN_EXPORT PerlinNoice& PerlinNoice::operator = (PerlinNoice&& other)
        {
            if(this != &other )
            {
                width = other.width;
                height = other.height;
                amplitude = other.amplitude;
                frequency = other.frequency;
                persistence = other.persistence;
                octaves = other.octaves;
                zStart = other.zStart;
                pixels = other.pixels;
                other.pixels = nullptr;
            }
            return *this;
        }


        HWIN_EXPORT int* PerlinNoice::Pixels()
        {
            if(pixels == nullptr)
            {
                pixels = new int[width*height];
                GenerateNoise(pixels,width,height,amplitude, frequency, persistence, octaves, zStart);
            }
            return pixels; 
        }


    };
};